package com.distributed_lock_zk;

import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.Collections;
import java.util.List;

/**
 * ZK 分布式锁依赖主从服务之间同步来实现HA 由于网络同步性能与ZK各个服务之间的网络有关。如果网络波动可能导致服务同步不及时
 * 所以适合并发量不太高的分布式场景。
 *
 * ZK 分布式锁 利用ZK的顺序临时节点
 *  1. 先创建一个 lock 节点
 *  2. 线程 A 进来在lock 下创建一个 临时循序节点
 *  3  线程 B 进来在lock 下创建一个 临时顺序节点
 *  4  线程 A 获取lock 下的全部子节点 判断自己的序号是不是最小的 如果是则获取锁 进行业务处理 处理完毕删除节点。
 *  5  线程 B 获取lock 下的全部子节点 判断自己是不是最小节点 不是则 监听自己的上一个一个节点 当监听自己的上一个节点被删除时 重复 4步 获取锁
 */
@Slf4j
public class ZkLockImpl {
    private static final String ROOT_PATH = "/test/lock";
    private static final String LOCK_PREFIX = ROOT_PATH + "/";
    private static ZkClient zkClient;
    private String sequential = "";

    public ZkLockImpl() {
        synchronized (this){
            zkClient = new ZkClient("127.0.0.1:2181");
            if (!zkClient.exists(ROOT_PATH)) {
                zkClient.createPersistent(ROOT_PATH,true);
            }
        }
    }

    public boolean tryLock() {
        try {
            //创建该线程的zNode
            sequential = zkClient.createEphemeralSequential(LOCK_PREFIX, null);
            log.info("创建了 {}", sequential);
            // 获取当前节点下的全部子节点
            List<String> children = zkClient.getChildren(ROOT_PATH);
            // 排序
            Collections.sort(children);

            if (sequential.equals(ROOT_PATH + "/" +children.get(0))) {
                return true;
            }
            if (children.size() > 1) {
                String per_path = ROOT_PATH + "/" + children.get(1);
                zkClient.subscribeDataChanges(per_path, new IZkDataListener() {
                    @Override
                    public void handleDataChange(String s, Object o) throws Exception {

                    }

                    @Override
                    public void handleDataDeleted(String s) throws Exception {
                        tryLock();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
            zkClient.delete(sequential);
        }
        return false;
    }

    public void unLock(){
        log.info("删除 {}",sequential);
        zkClient.delete(sequential);
    }
}
